package com.plumecache.core.interceptor;

import com.plumecache.core.CacheService;
import com.plumecache.core.annotation.IgnoreInterceptor;
import com.plumecache.core.exception.PlumeCacheException;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
public class CacheInterceptorProxyInvocationHandler implements InvocationHandler {

    private CacheService cacheService;

    private final CacheInterceptorChain CACHE_INTERCEPTOR_CHAIN;

    public CacheInterceptorProxyInvocationHandler(CacheService target) {
        this.cacheService = target;
        CACHE_INTERCEPTOR_CHAIN = initializeCacheInterceptorChain();
    }

    @SneakyThrows
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) {
        if (!(proxy instanceof CacheService)) {
            throw new PlumeCacheException("not support target:" + proxy.getClass());
        }

        if (method.getAnnotation(IgnoreInterceptor.class) != null) {
            return method.invoke(cacheService, args);
        }

        HashMap<String, Object> context = new HashMap<>();

        try {
            CACHE_INTERCEPTOR_CHAIN.applyPreHandle(cacheService, method, args, context);
            Object result = method.invoke(cacheService, args);
            CACHE_INTERCEPTOR_CHAIN.applyPostHandle(cacheService, method, args, context, result);
            return result;
        } catch (Exception ex) {
            CACHE_INTERCEPTOR_CHAIN.triggerAfterCompletion(cacheService, method, args, context, ex);
            throw new PlumeCacheException(ex);
        }
    }

    public CacheInterceptorChain initializeCacheInterceptorChain() {
        ServiceLoader<CacheInterceptor> serviceLoader = ServiceLoader.load(CacheInterceptor.class);
        CacheInterceptorChain cacheInterceptorChain = new CacheInterceptorChain();
        List<CacheInterceptor> cacheInterceptors = new ArrayList<>();

        List<String> excludeInterceptors = Optional.ofNullable(cacheService.getProperties().getExcludeInterceptors()).orElse(Collections.emptyList());
        HashSet<String> excludeInterceptorSet = new HashSet<>(excludeInterceptors);

        serviceLoader.forEach(cacheInterceptors::add);
        List<CacheInterceptor> interceptorList = cacheInterceptors.stream()
                .filter(CacheInterceptor::isEnable)
                .filter(r -> !excludeInterceptorSet.contains(r.getClass().getName()))
                .sorted(Comparator.comparingInt(CacheInterceptor::getOrder)).collect(Collectors.toList());

        cacheInterceptorChain.setInterceptors(interceptorList);
        return cacheInterceptorChain;
    }
}
